﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace RVBike
{
    class RVGameClient
    {
        private string _myIP;
        private IPAddress _serverIP;
        private IPAddress _mcastIP;
        private UdpClient _serverCon;
        private UdpClient _mcastCon;
        private Thread _mcastRecvThread;

        public RVNetworkEnv.JoinRoomHandlers JoinRoomHandler { get; set; }
        public RVNetworkEnv.LeaveRoomHandlers LeaveRoomHandler { get; set; }
        public RVNetworkEnv.ClientStateChangeHandlers ClientStateChangeHandler { get; set; }
        public RVNetworkEnv.RoomInfoUpdateHandlers RoomInfoUpdateHandler { get; set; }
        public RVNetworkEnv.QuitRoomHandlers QuitRoomHandler { get; set; }
        public RVNetworkEnv.GameStartHandlers GameStartHandler { get; set; }
        public RVNetworkEnv.StartPlayHandlers StartPlayingHandler { get; set; }

        public RVNetworkEnv.ReceiveMsgHandlers ReceiveMsgHandler { get; set; }

        public string Name { get; set; }

        public RVGameClient(string name, string serverIP, string mcastIP)
        {
            Name = name;

            _myIP = NetworkUtility.MyIP();
            _serverIP = IPAddress.Parse(serverIP);
            _mcastIP = IPAddress.Parse(mcastIP);

            // Setup communication with server (unicast)
            _serverCon = new UdpClient();

            _serverCon.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            _serverCon.ExclusiveAddressUse = false;
            _serverCon.Client.Bind(new IPEndPoint(IPAddress.Any, RVNetworkEnv.CLIENT_PORT));

            // Setup multicast receiver
            _mcastCon = new UdpClient();
            _mcastCon.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            _mcastCon.ExclusiveAddressUse = false;
            _mcastCon.Client.Bind(new IPEndPoint(IPAddress.Any, RVNetworkEnv.MULTICAST_PORT));
            
            //_mcastCon.JoinMulticastGroup(_mcastIP);
            
            _mcastRecvThread = new Thread(() => receiveMcastMsg());
            _mcastRecvThread.Start();
        }

        public void Close()
        {
            _serverCon.Close();
            _serverCon = null;

            _mcastCon.Client.Shutdown(SocketShutdown.Both);
            _mcastCon.Close();
            _mcastCon = null;

            // [FIXME]: How to shutdown socket gracefully?
            try
            {
                //_mcastRecvThread.Abort();
                //_mcastRecvThread.Join();
            }
            catch { }
            
            //_mcastRecvThread = null;
            
        }

        public bool JoinRoom()
        {
            Dictionary<string, string> msg = new Dictionary<string, string>();
            msg.Add(RVNetworkEnv.COMMAND, RVNetworkEnv.JOIN_ROOM);
            msg.Add(RVNetworkEnv.USER_NAME, Name);
            msg.Add(RVNetworkEnv.CLIENT_IP, _myIP);
            msg.Add(RVNetworkEnv.CLIENT_STATE, RVNetworkEnv.CLIENT_STATE_NOT_READY);
            SendMsg(msg);

            Dictionary<string, string> reply = receiveServerMsg();
            if (reply == null) return false;

            if (reply[RVNetworkEnv.RESULT] == "OK") return true;
            else return false;
        }

        public bool LeaveRoom()
        {
            Dictionary<string, string> msg = new Dictionary<string, string>();
            msg.Add(RVNetworkEnv.COMMAND, RVNetworkEnv.LEAVE_ROOM);
            msg.Add(RVNetworkEnv.CLIENT_IP, _myIP);
            SendMsg(msg);

            Dictionary<string, string> reply = receiveServerMsg();
            if (reply == null) return false;

            if (reply[RVNetworkEnv.RESULT] == "OK") return true;
            else return false;
        }

        public bool Ping()
        {
            Dictionary<string, string> msg = new Dictionary<string, string>();
            msg.Add(RVNetworkEnv.COMMAND, RVNetworkEnv.PING);
            msg.Add(RVNetworkEnv.CLIENT_IP, _myIP);
            SendMsg(msg);

            Dictionary<string, string> reply = receiveServerMsg();
            if (reply == null) return false;

            if (reply[RVNetworkEnv.RESULT] == "OK") return true;
            else return false;
        }

        public RVRoom QueryRoomInfo()
        {
            Dictionary<string, string> msg = new Dictionary<string, string>();
            msg.Add(RVNetworkEnv.COMMAND, RVNetworkEnv.QUERY_ROOM_INFO);
            msg.Add(RVNetworkEnv.CLIENT_IP, _myIP);
            SendMsg(msg);

            Dictionary<string, string> reply = receiveServerMsg();
            return new RVRoom(reply);
        }

        public bool UpdateInfo(string state, float mileage, string gps)
        {
            Dictionary<string, string> msg = new Dictionary<string, string>();
            msg.Add(RVNetworkEnv.COMMAND, RVNetworkEnv.UPDATE_STATE);
            msg.Add(RVNetworkEnv.CLIENT_IP, _myIP);
            msg.Add(RVNetworkEnv.CLIENT_STATE, state);
            msg.Add(RVNetworkEnv.GPS, gps);
            msg.Add(RVNetworkEnv.MILEAGE, mileage.ToString());

            SendMsg(msg);

            Dictionary<string, string> reply = receiveServerMsg();
            if (reply == null) return false;

            if (reply[RVNetworkEnv.RESULT] == "OK") return true;
            else return false;
        }

        public bool GameStart()
        {
            Dictionary<string, string> msg = new Dictionary<string, string>();
            msg.Add(RVNetworkEnv.COMMAND, RVNetworkEnv.GAME_START);
            msg.Add(RVNetworkEnv.CLIENT_IP, _myIP);

            SendMsg(msg);

            Dictionary<string, string> reply = receiveServerMsg();
            if (reply == null) return false;

            if (reply[RVNetworkEnv.RESULT] == "OK") return true;
            else return false;
        }

        private void SendVideo(Dictionary<string, string> msg, byte[] payload)
        {
            StringBuilder sb = new StringBuilder();

            // Add packet type
            sb.Append(RVNetworkEnv.PACKET_VIDEO).Append("?");

            bool isFirst = true;
            foreach (KeyValuePair<string, string> kvp in msg)
            {
                if (!isFirst) sb.Append("&");
                sb.Append(kvp.Key).Append("=").Append(kvp.Value);
                isFirst = false;
            }

            // prepare to add video payload
            sb.Append("&");

            IPEndPoint ep = new IPEndPoint(_serverIP, RVNetworkEnv.SERVER_PORT);
            byte[] header = Encoding.UTF8.GetBytes(sb.ToString());
            byte[] data = new byte[header.Length + payload.Length];
            header.CopyTo(data, 0);
            payload.CopyTo(data, header.Length);
            _serverCon.Send(data, data.Length, ep);
        }

        private void SendAudio(Dictionary<string, string> msg, byte[] payload)
        {
            StringBuilder sb = new StringBuilder();

            // Add packet type
            sb.Append(RVNetworkEnv.PACKET_AUDIO).Append("?");

            bool isFirst = true;
            foreach (KeyValuePair<string, string> kvp in msg)
            {
                if (!isFirst) sb.Append("&");
                sb.Append(kvp.Key).Append("=").Append(kvp.Value);
                isFirst = false;
            }

            // prepare to add video payload
            sb.Append("&");

            IPEndPoint ep = new IPEndPoint(_serverIP, RVNetworkEnv.SERVER_PORT);
            byte[] header = Encoding.UTF8.GetBytes(sb.ToString());
            byte[] data = new byte[header.Length + payload.Length];
            header.CopyTo(data, 0);
            payload.CopyTo(data, header.Length);
            _serverCon.Send(data, data.Length, ep);
        }

        private void SendMsg(Dictionary<string, string> msg)
        {
            StringBuilder sb = new StringBuilder();

            // Add packet type
            sb.Append(RVNetworkEnv.PACKET_MESSAGE).Append("?");

            bool isFirst = true;
            foreach (KeyValuePair<string, string> kvp in msg)
            {
                if (!isFirst) sb.Append("&");
                sb.Append(kvp.Key).Append("=").Append(kvp.Value);
                isFirst = false;
            }

            IPEndPoint ep = new IPEndPoint(_serverIP, RVNetworkEnv.SERVER_PORT);
            byte[] data = Encoding.UTF8.GetBytes(sb.ToString());

            _serverCon.Send(data, data.Length, ep);
        }

        private Dictionary<string, string> receiveServerMsg()
        {
            if (_serverCon == null) return null;

            IPEndPoint iep = new IPEndPoint(IPAddress.Any, RVNetworkEnv.CLIENT_PORT);

            try
            {
                byte[] data = _serverCon.Receive(ref iep);

                if (data[0] == RVNetworkEnv.PACKET_MESSAGE)
                {
                    string message = Encoding.UTF8.GetString(data);
                    System.Diagnostics.Trace.WriteLine(String.Format("Receive server message : {0}", message));
                    return ParseMsg(message);
                }
                else
                {
                    // We won't receive any audio/video packet using unicast/broadcast!!
                    System.Diagnostics.Trace.WriteLine("Receive audio/video packet in unicast connection");
                    return null;
                }
            }
            catch (System.Net.Sockets.SocketException)
            {
                System.Diagnostics.Trace.WriteLine("Receive error");
                return null;
            }
        }

        private void receiveMcastMsg()
        {
            if (_mcastCon == null) return;

            IPEndPoint iep = new IPEndPoint(IPAddress.Any, RVNetworkEnv.MULTICAST_PORT);

            try
            {
                while (true)
                {
                    byte[] data = _mcastCon.Receive(ref iep);

                    if (data[0] == RVNetworkEnv.PACKET_MESSAGE)
                    {
                        string message = Encoding.UTF8.GetString(data);
                        Dictionary<string, string> info = ParseMsg(message);

                        System.Diagnostics.Trace.WriteLine("Receive message using mcast");
                        if (info[RVNetworkEnv.COMMAND] == RVNetworkEnv.JOIN_ROOM)
                        {
                            if (JoinRoomHandler != null) JoinRoomHandler(info[RVNetworkEnv.USER_NAME], info[RVNetworkEnv.CLIENT_IP]);
                        }
                        else if (info[RVNetworkEnv.COMMAND] == RVNetworkEnv.LEAVE_ROOM)
                        {
                            if (LeaveRoomHandler != null) LeaveRoomHandler(info[RVNetworkEnv.CLIENT_IP]);
                        }
                        else if (info[RVNetworkEnv.COMMAND] == RVNetworkEnv.UPDATE_STATE)
                        {
                            if (ClientStateChangeHandler != null)
                            {
                                ClientStateChangeHandler(
                                    info[RVNetworkEnv.CLIENT_IP], 
                                    float.Parse(info[RVNetworkEnv.MILEAGE]), 
                                    info[RVNetworkEnv.CLIENT_STATE],
                                    info[RVNetworkEnv.GPS]);
                            }
                        }
                        else if (info[RVNetworkEnv.COMMAND] == RVNetworkEnv.QUIT)
                        {
                            if (QuitRoomHandler != null) QuitRoomHandler();
                            else
                            {
                                // If user not register callback, we need to kill by ourself!!
                                Thread killMe = new Thread(() => Close());
                                break;
                            }
                        }
                        else if (info[RVNetworkEnv.COMMAND] == RVNetworkEnv.GAME_START)
                        {
                            if (GameStartHandler != null) GameStartHandler();
                        }
                        else if (info[RVNetworkEnv.COMMAND] == RVNetworkEnv.START_PLAY)
                        {
                            if (StartPlayingHandler != null) StartPlayingHandler();
                        }

                    }
                    else if (data[0] == RVNetworkEnv.PACKET_VIDEO)
                    {
                        System.Diagnostics.Trace.WriteLine("Receive video using mcast");
                    }
                    else if (data[0] == RVNetworkEnv.PACKET_AUDIO)
                    {
                        System.Diagnostics.Trace.WriteLine("Receive audio using mcast");
                    }
                    else
                    {
                        System.Diagnostics.Trace.WriteLine("Receive unknown packet using mcast");
                    }
                }
            }
            catch (System.Threading.ThreadAbortException)
            {
            }
            catch (System.Exception)
            {
                System.Diagnostics.Trace.WriteLine("UDP to server socket closed");
            }
            
        }

        private Dictionary<string, string> ParseMsg(string message)
        {
            // Convert string to key-value parameter pairs
            Dictionary<string, string> info = new Dictionary<string, string>();
            string[] parameters = message.Substring(2).Split('&');  // skip packet type "M?"
            foreach (string s in parameters)
            {
                string[] keyVal = s.Split('=');
                info.Add(keyVal[0], keyVal[1]);
            }

            // callback for receive message
            if (ReceiveMsgHandler != null) ReceiveMsgHandler(info);

            return info;
        }
    }
}
